{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "toc-hr-collapsed": false
   },
   "source": [
    "# 6、与学习相关的技巧"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.1、参数的更新"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "神经网络的学习的目的是找到使损失函数的值尽可能小的参数。  \n",
    "这是寻找最优参数的问题，解决这个问题的过程称为**最优化**（optimization）。  \n",
    "遗憾的是，神经网络的最优化问题非常难。  \n",
    "这是因为参数空间非常复杂，无法轻易找到最优解（无法使用那种通过解数学式一下子就求得最小值的方法）。  \n",
    "而且，在深度神经网络中，参数的数量非常庞大，导致最优化问题更加复杂。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在前几章中，为了找到最优参数，我们将参数的梯度（导数）作为了线索。  \n",
    "使用参数的梯度，沿梯度方向更新参数，并重复这个步骤多次，从而逐渐靠近最优参数，这个过程称为**随机梯度下降法**（stochastic gradient descent），简称**SGD**。  \n",
    "SGD是一个简单的方法，不过比起胡乱地搜索参数空间，也算是“聪\n",
    "明”的方法。但是，根据不同的问题，也存在比SGD更加聪明的方法。  \n",
    "本节我们将指出SGD的缺点，并介绍SGD以外的其他最优化方法。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.1.1、探险家的故事"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "进入正题前，我们先打一个比方，来说明关于最优化我们所处的状况。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`有一个性情古怪的探险家。他在广袤的干旱地带旅行，坚持寻找幽深的山谷。  \n",
    "他的目标是要到达最深的谷底（他称之为“至深之地”）。  \n",
    "这也是他旅行的目的。  \n",
    "并且，他给自己制定了两个严格的“规定”：一个是不看地图；另一个是把眼睛蒙上。  \n",
    "因此，他并不知道最深的谷底在这个广袤的大地的何处，而且什么也看不见。  \n",
    "在这么严苛的条件下，这位探险家如何前往“至深之地”呢？  \n",
    "他要如何迈步，才能迅速找到“至深之地”呢？`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "寻找最优参数时，我们所处的状况和这位探险家一样，是一个漆黑的世界。  \n",
    "我们必须在没有地图、不能睁眼的情况下，在广袤、复杂的地形中寻找“至深之地”。  \n",
    "大家可以想象这是一个多么难的问题。  \n",
    "在这么困难的状况下，地面的坡度显得尤为重要。  \n",
    "探险家虽然看不到周围的情况，但是能够知道当前所在位置的坡度（通过脚底感受地面的倾斜状况）。  \n",
    "于是，朝着当前所在位置的坡度最大的方向前进，就是SGD的策略。  \n",
    "勇敢的探险家心里可能想着只要重复这一策略，总有一天可以到达“至深之地”。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.1.2、SGD"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "让大家感受了最优化问题的难度之后，我们再来复习一下SGD。用数\n",
    "学式可以将SGD写成如下的式（6.1）。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\n",
    "\\boldsymbol { W } \\leftarrow \\boldsymbol { W } - \\eta \\frac { \\partial L } { \\partial \\boldsymbol { W } }\n",
    "$$\n",
    "<center>式（6.1）</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里把需要更新的权重参数记为W，把损失函数关于W的梯度记为$$\n",
    "\\frac { \\partial L } { \\partial W }\n",
    "$$。\n",
    "η表示学习率，实际上会取0.01或0.001这些事先决定好的值。式子中的←表示用右边的值更新左边的值。  \n",
    "如式（6.1）所示， SGD是朝着梯度方向只前进一定距离的简单方法。  \n",
    "现在，我们将SGD实现为一个Python类（为方便后面使用，我们将其实现为一个名为SGD的类）  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SGD:\n",
    "    def __init__(self, lr=0.01):\n",
    "        self.lr = lr\n",
    "    def update(self, params, grads):\n",
    "        for key in params.keys():\n",
    "            params[key] -= self.lr * grads[key]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里，进行初始化时的参数lr表示learning rate（学习率）。  \n",
    "这个学习率会保存为实例变量。  \n",
    "此外，代码段中还定义了 update(params, grads)方法，这个方法在SGD中会被反复调用。  \n",
    "参数 params和 grads（与之前的神经网络的实现一样）是字典型变量，按params['W1']、 grads['W1']的形式，分别保存了权重参数和它们的梯度。  \n",
    "使用这个 SGD类，可以按如下方式进行神经网络的参数的更新（下面的代码是不能实际运行的伪代码）。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用这个 SGD类，可以按如下方式进行神经网络的参数的更新（下面的\n",
    "代码是不能实际运行的伪代码）："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "network = TwoLayerNet(...)\n",
    "optimizer = SGD()\n",
    "\n",
    "for i in range(10000):\n",
    "    ...\n",
    "    x_batch, t_batch = get_mini_batch(...) # mini-batch\n",
    "    grads = network.gradient(x_batch, t_batch)\n",
    "    params = network.params\n",
    "    optimizer.update(params, grads)\n",
    "    ...\n",
    "'''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里首次出现的变量名optimizer表示“进行最优化的人”的意思，这里由SGD承担这个角色。  \n",
    "参数的更新由 optimizer负责完成。我们在这里需要做的只是将参数和梯度的信息传给optimizer。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "像这样，通过单独实现进行最优化的类，功能的模块化变得更简单。  \n",
    "比如，后面我们马上会实现另一个最优化方法Momentum，它同样会实现成拥有update(params, grads)这个共同方法的形式。  \n",
    "这样一来，只需要将optimizer = SGD()这一语句换成 optimizer = Momentum()，就可以从SGD切换为Momentum。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "很多深度学习框架都实现了各种最优化方法，并且提供了可以简单切换这些方法的构造。  \n",
    "比如 Lasagne 深度学习框架，在 updates.py这个文件中以函数的形式集中实现了最优化方法。  \n",
    "用户可以从中选择自己想用的最优化方法。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.1.3、SGD的缺点"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "虽然SGD简单，并且容易实现，但是在解决某些问题时可能没有效率。  \n",
    "这里，在指出SGD的缺点之际，我们来思考一下求下面这个函数的最小值的问题。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\n",
    "f ( x , y ) = \\frac { 1 } { 20 } x ^ { 2 } + y ^ { 2 }\n",
    "$$\n",
    "<center>式（6.2）</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如图6-1所示，式（6.2）表示的函数是向x轴方向延伸的“碗”状函数。  \n",
    "实际上，式（6.2）的等高线呈向x轴方向延伸的椭圆状。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_1.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在看一下式（6.2）表示的函数的梯度。如果用图表示梯度的话，则如图6-2所示。  \n",
    "这个梯度的特征是， y轴方向上大， x轴方向上小。  \n",
    "换句话说，就是y轴方向的坡度大，而x轴方向的坡度小。  \n",
    "这里需要注意的是，虽然式（6.2）的最小值在(x, y) = (0, 0)处，但是图6-2中的梯度在很多地方并没有指向(0, 0)。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_2.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们来尝试对图6-1这种形状的函数应用SGD。从(x, y) = (−7.0, 2.0)处（初始值）开始搜索，结果如图6-3所示。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_3.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在图6-3中， SGD呈“之”字形移动。这是一个相当低效的路径。  \n",
    "也就是说，SGD的缺点是，如果函数的形状非均向（anisotropic），比如呈延伸状，搜索的路径就会非常低效。  \n",
    "因此，我们需要比单纯朝梯度方向前进的SGD更聪明的方法。   \n",
    "SGD低效的根本原因是，梯度的方向并没有指向最小值的方向。  \n",
    "为了改正SGD的缺点，下面我们将介绍Momentum、 AdaGrad、 Adam这3种方法来取代SGD。  \n",
    "我们会简单介绍各个方法，并用数学式和Python进行实现。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.1.4、Momentum"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Momentum是“动量”的意思，和物理有关。用数学式表示Momentum方法，如下所示。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\n",
    "\\boldsymbol { v } \\leftarrow \\alpha \\boldsymbol { v } - \\eta \\frac { \\partial L } { \\partial \\boldsymbol { W } }\n",
    "$$\n",
    "<center>式（6.3）</center><br>\n",
    "$$\n",
    "\\boldsymbol { W } \\leftarrow \\boldsymbol { W } + \\boldsymbol { v }\n",
    "$$\n",
    "<center>式（6.4）</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "和前面的SGD一样， W表示要更新的权重参数， $$\n",
    "\\frac { \\partial L } { \\partial \\boldsymbol { W } }\n",
    "$$表示损失函数关于W的梯度， η表示学习率。  \n",
    "这里新出现了一个变量v，对应物理上的速度。  \n",
    "式（6.3）表示了物体在梯度方向上受力，在这个力的作用下，物体的速度增加这一物理法则。  \n",
    "如图6-4所示， Momentum方法给人的感觉就像是小球在地面上滚动。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_4.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "式（6.3）中有αv这一项。在物体不受任何力时，该项承担使物体逐渐减\n",
    "速的任务（α设定为0.9之类的值），对应物理上的地面摩擦或空气阻力。下\n",
    "面是Momentum的代码实现:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Momentum:\n",
    "    def __init__(self, lr=0.01, momentum=0.9):\n",
    "        self.lr = lr\n",
    "        self.momentum = momentum\n",
    "        self.v = None\n",
    "    def update(self, params, grads):\n",
    "        if self.v is None:\n",
    "            self.v = {}\n",
    "            for key, val in params.items():\n",
    "                self.v[key] = np.zeros_like(val)\n",
    "        for key in params.keys():\n",
    "            self.v[key] = self.momentum*self.v[key] - self.lr*grads[key]\n",
    "            params[key] += self.v[key]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "实例变量 v会保存物体的速度。  \n",
    "初始化时， v中什么都不保存，但当第一次调用update()时， v会以字典型变量的形式保存与参数结构相同的数据。  \n",
    "剩余的代码部分就是将式（6.3）、式（6.4）写出来，很简单。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在尝试使用Momentum解决式（6.2）的最优化问题，如图6-5所示。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_5.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "图6-5中，更新路径就像小球在碗中滚动一样。  \n",
    "和SGD相比，我们发现“之”字形的“程度”减轻了。  \n",
    "这是因为虽然x轴方向上受到的力非常小，但是一直在同一方向上受力，所以朝同一个方向会有一定的加速。  \n",
    "反过来，虽然y轴方向上受到的力很大，但是因为交互地受到正方向和反方向的力，它们会互相抵消，所以y轴方向上的速度不稳定。  \n",
    "因此，和SGD时的情形相比，可以更快地朝x轴方向靠近，减弱“之”字形的变动程度。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.1.5、AdaGrad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在神经网络的学习中，学习率（数学式中记为η）的值很重要。  \n",
    "学习率过小，会导致学习花费过多时间；反过来，学习率过大，则会导致学习发散而不能\n",
    "正确进行。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在关于学习率的有效技巧中，有一种被称为**学习率衰减**（learning rate decay）的方法，即随着学习的进行，使学习率逐渐减小。  \n",
    "实际上，一开始“多”学，然后逐渐“少”学的方法，在神经网络的学习中经常被使用。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "逐渐减小学习率的想法，相当于将“全体”参数的学习率值一起降低。  \n",
    "而AdaGrad进一步发展了这个想法，针对“一个一个”的参数，赋予其“定制”的值。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "AdaGrad会为参数的每个元素适当地调整学习率，与此同时进行学习（AdaGrad的Ada来自英文单词Adaptive，即“适当的”的意思）。  \n",
    "下面，让我们用数学式表示AdaGrad的更新方法。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\n",
    "\\boldsymbol { h } \\leftarrow \\boldsymbol { h } + \\frac { \\partial L } { \\partial \\boldsymbol { W } } \\odot \\frac { \\partial L } { \\partial \\boldsymbol { W } }\n",
    "$$\n",
    "<center>式（6.5）</center><br>\n",
    "$$\n",
    "\\boldsymbol { W } \\leftarrow \\boldsymbol { W } - \\eta \\frac { 1 } { \\sqrt { \\boldsymbol { h } } } \\frac { \\partial L } { \\partial \\boldsymbol { W } }\n",
    "$$\n",
    "<center>式（6.6）</center><br>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "和前面的SGD一样， W表示要更新的权重参数， 表示损失函数关于W的梯度， η表示学习率。  \n",
    "这里新出现了变量h，如式(6.5)所示，它保存了以前的所有梯度值的平方和（式（6.5）中的表示对应矩阵元素的乘法）。  \n",
    "然后，在更新参数时，通过乘以 ，就可以调整学习的尺度。  \n",
    "这意味着，参数的元素中变动较大（被大幅更新）的元素的学习率将变小。  \n",
    "也就是说，可以按参数的元素进行学习率衰减，使变动大的参数的学习率逐渐减小。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "AdaGrad会记录过去所有梯度的平方和。因此，学习越深入，更新的幅度就越小。  \n",
    "实际上，如果无止境地学习，更新量就会变为 0，完全不再更新。  \n",
    "为了改善这个问题，可以使用 **RMSProp**方法。  \n",
    "RMSProp方法并不是将过去所有的梯度一视同仁地相加，而是逐渐地遗忘过去的梯度，在做加法运算时将新梯度的信息更多地反映出来。  \n",
    "这种操作从专业上讲，称为“指数移动平均”，呈指数函数式地减小过去的梯度的尺度。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现 在 来 实 现 AdaGrad。 AdaGrad 的 实 现 过 程 如 下 所 示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class AdaGrad:\n",
    "    def __init__(self, lr=0.01):\n",
    "        self.lr = lr\n",
    "        self.h = None\n",
    "    def update(self, params, grads):\n",
    "        if self.h is None:\n",
    "            self.h = {}\n",
    "            for key, val in params.items():\n",
    "                self.h[key] = np.zeros_like(val)\n",
    "        for key in params.keys():\n",
    "            self.h[key] += grads[key] * grads[key]\n",
    "            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里需要注意的是，最后一行加上了微小值 1e-7。  \n",
    "这是为了防止当self.h[key]中有0时，将0用作除数的情况。  \n",
    "在很多深度学习的框架中，这个微小值也可以设定为参数，但这里我们用的是1e-7这个固定值。  \n",
    "现在，让我们试着使用AdaGrad解决式（6.2）的最优化问题，结果如图6-6所示：  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_6.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "由图6-6的结果可知，函数的取值高效地向着最小值移动。  \n",
    "由于y轴方向上的梯度较大，因此刚开始变动较大，但是后面会根据这个较大的变动按比例进行调整，减小更新的步伐。  \n",
    "因此， y轴方向上的更新程度被减弱，“之”字形的变动程度有所衰减。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.1.6、Adam"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Momentum参照小球在碗中滚动的物理规则进行移动， AdaGrad为参数的每个元素适当地调整更新步伐。  \n",
    "如果将这两个方法融合在一起会怎么样呢？这就是Adam方法的基本思路。  \n",
    "Adam是2015年提出的新方法。  \n",
    "它的理论有些复杂，直观地讲，就是融合了Momentum和AdaGrad的方法。  \n",
    "通过组合前面两个方法的优点，有望实现参数空间的高效搜索。  \n",
    "此外，进行超参数的“偏置校正”也是Adam的特征。  \n",
    "这里不再进行过多的说明，详细内容请参考原作者的论文。  \n",
    "关于Python的实现， common/optimizer.py中将其实现为了Adam类，有兴趣的读者可以参考。  \n",
    "现在，我们试着使用Adam解决式（6.2）的最优化问题，结果如图6-7所示。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_7.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在图6-7中，基于Adam的更新过程就像小球在碗中滚动一样。  \n",
    "虽然Momentun也有类似的移动，但是相比之下， Adam的小球左右摇晃的程度有所减轻。  \n",
    "这得益于学习的更新程度被适当地调整了。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Adam会设置3个超参数。  \n",
    "一个是学习率（论文中以α出现），另外两个是一次momentum系数β1和二次momentum系数β2。  \n",
    "根据论文，标准的设定值是β1为0.9， β2为0.999。  \n",
    "设置了这些值后，大多数情况下都能顺利运行。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.1.7、使用哪种更新方法呢"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "到目前为止，我们已经学习了4种更新参数的方法。  \n",
    "这里我们来比较一下这4种方法（源代码在ch06/optimizer_compare_naive.py中）。  \n",
    "\n",
    "如图6-8所示，根据使用的方法不同，参数更新的路径也不同。  \n",
    "只看这个图的话， AdaGrad似乎是最好的，不过也要注意，结果会根据要解决的问题而变。  \n",
    "并且，很显然，超参数（学习率等）的设定值不同，结果也会发生变化。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_8.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上面我们介绍了SGD、 Momentum、 AdaGrad、 Adam这4种方法，那\n",
    "么用哪种方法好呢？  \n",
    "非常遗憾，（目前）并不存在能在所有问题中都表现良好的方法。  \n",
    "这4种方法各有各的特点，都有各自擅长解决的问题和不擅长解决的问题。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "很多研究中至今仍在使用SGD。   \n",
    "Momentum和AdaGrad也是值得一试的方法。  \n",
    "最近，很多研究人员和技术人员都喜欢用Adam。  \n",
    "本书将主要使用SGD或者Adam，读者可以根据自己的喜好多多尝试。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.1.8、基于MNIST数据集的更新方法的比较"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我 们 以 手 写 数 字 识 别 为 例，比 较 前 面 介 绍 的 SGD、 Momentum、\n",
    "AdaGrad、 Adam这4种方法，并确认不同的方法在学习进展上有多大程度的差异。  \n",
    "先来看一下结果，如图6-9所示（源代码在 ch06/optimizer_compare_\n",
    "mnist.py中）。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_9.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这个实验以一个5层神经网络为对象，其中每层有100个神经元。  \n",
    "激活函数使用的是ReLU。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从图6-9的结果中可知，与SGD相比，其他3种方法学习得更快，而且速度基本相同，仔细看的话，AdaGrad的学习进行得稍微快一点。  \n",
    "这个实验需要注意的地方是，实验结果会随学习率等超参数、神经网络的结构（几层深等）的不同而发生变化。  \n",
    "不过，一般而言，与SGD相比，其他3种方法可以学习得更快，有时最终的识别精度也更高。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.2、权重的初始值"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在神经网络的学习中，权重的初始值特别重要。  \n",
    "实际上，设定什么样的权重初始值，经常关系到神经网络的学习能否成功。  \n",
    "本节将介绍权重初始值的推荐值，并通过实验确认神经网络的学习是否会快速进行。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.2.1、可以将权重初始值设为0吗"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "后面我们会介绍抑制过拟合、提高泛化能力的技巧——权值衰减（weight\n",
    "decay）。  \n",
    "简单地说，权值衰减就是一种以减小权重参数的值为目的进行学习的方法。  \n",
    "通过减小权重参数的值来抑制过拟合的发生。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果想减小权重的值，一开始就将初始值设为较小的值才是正途。  \n",
    "实际上，在这之前的权重初始值都是像 0.01 * np.random.randn(10, 100)这样，使用由高斯分布生成的值乘以0.01后得到的值（标准差为0.01的高斯分布）。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果我们把权重初始值全部设为0以减小权重的值，会怎么样呢？  \n",
    "从结论来说，将权重初始值设为0不是一个好主意。  \n",
    "事实上，将权重初始值设为0的话，将无法正确进行学习。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为什么不能将权重初始值设为0呢？  \n",
    "严格地说，为什么不能将权重初始值设成一样的值呢？  \n",
    "这是因为在误差反向传播法中，所有的权重值都会进行相同的更新。  \n",
    "比如，在2层神经网络中，假设第1层和第2层的权重为0。  \n",
    "这样一来，正向传播时，因为输入层的权重为0，所以第2层的神经元全部会被传递相同的值。  \n",
    "第2层的神经元中全部输入相同的值，这意味着反向传播时第2层的权重全部都会进行相同的更新（回忆一下“乘法节点的反向传播”的内容）。  \n",
    "因此，权重被更新为相同的值，并拥有了对称的值（重复的值）。  \n",
    "这使得神经网络拥有许多不同的权重的意义丧失了。  \n",
    "为了防止“权重均一化”（严格地讲，是为了瓦解权重的对称结构），必须随机生成初始值。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.2.2、隐藏层的激活值的分布"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "观察隐藏层的激活值（激活函数的输出数据）的分布，可以获得很多启发。  \n",
    "这里，我们来做一个简单的实验，观察权重初始值是如何影响隐藏层的激活值的分布的。  \n",
    "这里要做的实验是，向一个5层神经网络（激活函数使用sigmoid函数）传入随机生成的输入数据，用直方图绘制各层激活值的数据分布。  \n",
    "这个实验参考了斯坦福大学的课程CS231n [5]。  \n",
    "进行实验的源代码在 ch06/weight_init_activation_histogram.py中，下面展示部分代码。  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def sigmoid(x):\n",
    "    return 1 / (1 + np.exp(-x))\n",
    "\n",
    "x = np.random.randn(1000, 100) # 1000个数据\n",
    "node_num = 100 # 各隐藏层的节点（神经元）数\n",
    "hidden_layer_size = 5 # 隐藏层有5层\n",
    "activations = {} # 激活值的结果保存在这里\n",
    "\n",
    "for i in range(hidden_layer_size):\n",
    "    if i != 0:\n",
    "        x = activations[i-1]\n",
    "        \n",
    "    w = np.random.randn(node_num, node_num) * 1\n",
    "    \n",
    "    z = np.dot(x, w)\n",
    "    a = sigmoid(z) # sigmoid函数\n",
    "    activations[i] = a"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里假设神经网络有5层，每层有100个神经元。  \n",
    "然后，用高斯分布随机生成1000个数据作为输入数据，并把它们传给5层神经网络。  \n",
    "激活函数使用sigmoid函数，各层的激活值的结果保存在activations变量中。  \n",
    "这个代码段中需要注意的是权重的尺度。  \n",
    "虽然这次我们使用的是标准差为1的高斯分布，但实验的目的是通过改变这个尺度（标准差），观察激活值的分布如何变化。  \n",
    "现在，我们将保存在activations中的各层数据画成直方图。  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 5 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 绘制直方图\n",
    "for i, a in activations.items():\n",
    "    plt.subplot(1, len(activations), i+1)\n",
    "    plt.title(str(i+1) + \"-layer\")\n",
    "    plt.hist(a.flatten(), 30, range=(0,1))\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "图6-10　使用标准差为1的高斯分布作为权重初始值时的各层激活值的分布"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从图6-10可知，各层的激活值呈偏向0和1的分布。  \n",
    "这里使用的sigmoid函数是S型函数，随着输出不断地靠近0（或者靠近1），它的导数的值逐渐接\n",
    "近0。  \n",
    "因此，偏向0和1的数据分布会造成反向传播中梯度的值不断变小，最后消失。  \n",
    "这个问题称为**梯度消失**（gradient vanishing）。  \n",
    "层次加深的深度学习中，梯度消失的问题可能会更加严重。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面，将权重的标准差设为0.01，进行相同的实验。  \n",
    "实验的代码只需要把设定权重初始值的地方换成下面的代码即可。  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# w = np.random.randn(node_num, node_num) * 1\n",
    "w = np.random.randn(node_num, node_num) * 0.01"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "来看一下结果。使用标准差为0.01的高斯分布时，各层的激活值的分布如图6-11所示。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_11.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这次呈集中在0.5附近的分布。  \n",
    "因为不像刚才的例子那样偏向0和1，所以不会发生梯度消失的问题。  \n",
    "但是，激活值的分布有所偏向，说明在表现力上会有很大问题。  \n",
    "为什么这么说呢？因为如果有多个神经元都输出几乎相同的值，那它们就没有存在的意义了。  \n",
    "比如，如果100个神经元都输出几乎相同的值，那么也可以由1个神经元来表达基本相同的事情。  \n",
    "因此，激活值在分布上有所偏向会出现“表现力受限”的问题。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "各层的激活值的分布都要求有适当的广度。  \n",
    "为什么呢？因为通过在各层间传递多样性的数据，神经网络可以进行高效的学习。  \n",
    "反过来，如果传递的是有所偏向的数据，就会出现梯度消失或者“表现力受限”的问题，导致学习可能无法顺利进行。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "接着，我们尝试使用Xavier Glorot等人的论文中推荐的权重初始值（俗称“Xavier初始值”）。  \n",
    "现在，在一般的深度学习框架中， Xavier初始值已被作为标准使用。  \n",
    "比如， Caffe框架中，通过在设定权重初始值时赋予xavier参数，就可以使用Xavier初始值。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Xavier的论文中，为了使各层的激活值呈现出具有相同广度的分布，推导了合适的权重尺度。  \n",
    "推导出的结论是，如果前一层的节点数为n，则初始值使用标准差为$$\n",
    "\\frac { 1 } { \\sqrt { n } }\n",
    "$$的分布（图6-12）。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_12.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用Xavier初始值后，前一层的节点数越多，要设定为目标节点的初始值的权重尺度就越小。  \n",
    "现在，我们使用Xavier初始值进行实验。  \n",
    "进行实验的代码只需要将设定权重初始值的地方换成如下内容即可（因为此处所有层的\n",
    "节点数都是100，所以简化了实现）。  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "node_num = 100 # 前一层的节点数\n",
    "w = np.random.randn(node_num, node_num) / np.sqrt(node_num)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用Xavier初始值后的结果如图6-13所示。  \n",
    "从这个结果可知，越是后面的层，图像变得越歪斜，但是呈现了比之前更有广度的分布。  \n",
    "因为各层间传递的数据有适当的广度，所以sigmoid函数的表现力不受限制，有望进行高效的学习。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_13.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "图6-13的分布中，后面的层的分布呈稍微歪斜的形状。  \n",
    "如果用tanh函数（双曲线函数）代替 sigmoid函数，这个稍微歪斜的问题就能得到改善。  \n",
    "实际上，使用 tanh函数后，会呈漂亮的吊钟型分布。   \n",
    "tanh函数和sigmoid函数同是S型曲线函数，但tanh函数是关于原点(0, 0)对称的S型曲线，而 sigmoid函数是关于(x, y)=(0, 0.5)对称的S型曲线。  \n",
    "众所周知，用作激活函数的函数最好具有关于原点对称的性质。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.2.3、ReLU的权重初始值"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Xavier初始值是以激活函数是线性函数为前提而推导出来的。  \n",
    "因为sigmoid函数和 tanh函数左右对称，且中央附近可以视作线性函数，所以适合使用Xavier初始值。  \n",
    "但当激活函数使用ReLU时，一般推荐使用ReLU专用的初始值，也就是Kaiming He等人推荐的初始值，也称为“He初始值”。  \n",
    "当前一层的节点数为n时， He初始值使用标准差为$$\n",
    "\\sqrt { \\frac { 2 } { n } }\n",
    "$$的高斯分布。  \n",
    "当Xavier初始值是$$\n",
    "\\sqrt { \\frac { 1 } { n } }\n",
    "$$时，（直观上）可以解释为，因为ReLU的负值区域的值为0，为了使它更有广度，所以需要2倍的系数。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_14.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "观察实验结果可知，当“std = 0.01”时，各层的激活值非常小。  \n",
    "神经网络上传递的是非常小的值，说明逆向传播时权重的梯度也同样很小。  \n",
    "这是很严重的问题，实际上学习基本上没有进展。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "接下来是初始值为Xavier初始值时的结果。  \n",
    "在这种情况下，随着层的加深，偏向一点点变大。  \n",
    "实际上，层加深后，激活值的偏向变大，学习时会出现梯度消失的问题。  \n",
    "而当初始值为He初始值时，各层中分布的广度相同。  \n",
    "由于即便层加深，数据的广度也能保持不变，因此逆向传播时，也会传递合适的值。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "总结一下，当激活函数使用ReLU时，权重初始值使用He初始值，当激活函数为 sigmoid或 tanh等S型曲线函数时，初始值使用Xavier初始值。  \n",
    "这是目前的最佳实践。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.2.4、基于MNIST数据集的权重初始值的比较"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面通过实际的数据，观察不同的权重初始值的赋值方法会在多大程度上影响神经网络的学习。  \n",
    "这里，我们基于std = 0.01、 Xavier初始值、 He初始值进行实验（源代码在ch06/weight_init_compare.py中）。  \n",
    "先来看一下结果，如图6-15所示。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_15.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这个实验中，神经网络有5层，每层有100个神经元，激活函数使用的\n",
    "是ReLU。  \n",
    "从图6-15的结果可知， std = 0.01时完全无法进行学习。  \n",
    "这和刚才观察到的激活值的分布一样，是因为正向传播中传递的值很小（集中在0\n",
    "附近的数据）。  \n",
    "因此，逆向传播时求到的梯度也很小，权重几乎不进行更新。  \n",
    "相反，当权重初始值为Xavier初始值和He初始值时，学习进行得很顺利。  \n",
    "并且，我们发现He初始值时的学习进度更快一些。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "综上，在神经网络的学习中，权重初始值非常重要。  \n",
    "很多时候权重初始值的设定关系到神经网络的学习能否成功。  \n",
    "权重初始值的重要性容易被忽视，而任何事情的开始（初始值）总是关键的，因此在结束本节之际，再次强调一下权重初始值的重要性。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.3、Batch Normalization"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在上一节，我们观察了各层的激活值分布，并从中了解到如果设定了合适的权重初始值，则各层的激活值分布会有适当的广度，从而可以顺利地进行学习。  \n",
    "那么，为了使各层拥有适当的广度，“强制性”地调整激活值的分布会怎样呢？  \n",
    "实际上， Batch Normalization方法就是基于这个想法而产生的。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.3.1、Batch Normalization 的算法"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Batch Normalization（下文简称 Batch Norm）是 2015 年提出的方法。  \n",
    "Batch Norm虽然是一个问世不久的新方法，但已经被很多研究人员和技术人员广泛使用。  \n",
    "实际上，看一下机器学习竞赛的结果，就会发现很多通过使用这个方法而获得优异结果的例子。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为什么Batch Norm这么惹人注目呢？因为Batch Norm有以下优点。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1、可以使学习快速进行（可以增大学习率）。  \n",
    "2、不那么依赖初始值（对于初始值不用那么神经质）。  \n",
    "3、抑制过拟合（降低Dropout等的必要性）。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "考虑到深度学习要花费很多时间，第一个优点令人非常开心。  \n",
    "另外，后两点也可以帮我们消除深度学习的学习中的很多烦恼。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如前所述， Batch Norm的思路是调整各层的激活值分布使其拥有适当的广度。  \n",
    "为此，要向神经网络中插入对数据分布进行正规化的层，即Batch Normalization层（下文简称Batch Norm层），如图6-16所示。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_16.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Batch Norm，顾名思义，以进行学习时的mini-batch为单位，按minibatch进行正规化。  \n",
    "具体而言，就是进行使数据分布的均值为0、方差为1的正规化。  \n",
    "用数学式表示的话，如下所示。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\n",
    "\\begin{aligned} \\mu _ { B } & \\leftarrow \\frac { 1 } { m } \\sum _ { i = 1 } ^ { m } x _ { i } \\\\ \\sigma _ { B } ^ { 2 } & \\leftarrow \\frac { 1 } { m } \\sum _ { i = 1 } ^ { m } \\left( x _ { i } - \\mu _ { B } \\right) ^ { 2 } \\\\ \\hat { x } _ { i } & \\leftarrow \\frac { x _ { i } - \\mu _ { B } } { \\sqrt { \\sigma _ { B } ^ { 2 } + \\varepsilon } } \\end{aligned}\n",
    "$$\n",
    "<center>式（6.7）</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里对mini-batch的m个输入数据的集合B = {x1, x2, . . . , xm}求均值\n",
    "µB和方差$$\n",
    "\\sigma _ { B } ^ { 2 }\n",
    "$$。  \n",
    "然后，对输入数据进行均值为0、方差为1（合适的分布）的正规化。  \n",
    "式（6.7）中的ε是一个微小值（比如， 10e-7等），它是为了防止出现除以0的情况。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "式（6.7）所做的是将mini-batch的输入数据{x1, x2, . . . , xm}变换为均值为0、方差为1的数据 ，非常简单。  \n",
    "通过将这个处理插入到激活函数的前面（或者后面），可以减小数据分布的偏向。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "接着， Batch Norm层会对正规化后的数据进行缩放和平移的变换，用\n",
    "数学式可以如下表示。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\n",
    "y _ { i } \\leftarrow \\gamma \\hat { x } _ { i } + \\beta\n",
    "$$\n",
    "<center>式（6.8）</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里， γ和β是参数。  \n",
    "一开始γ = 1， β = 0，然后再通过学习调整到合适的值。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上面就是Batch Norm的算法。  \n",
    "这个算法是神经网络上的正向传播。  \n",
    "如果使用第5章介绍的计算图， Batch Norm可以表示为图6-17。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_17.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Batch Norm的反向传播的推导有些复杂，这里我们不进行介绍。  \n",
    "不过如果使用图6-17的计算图来思考的话， Batch Norm的反向传播或许也能比较轻松地推导出来。  \n",
    "Frederik Kratzert 的博客“Understanding the backward\n",
    "pass through Batch Normalization Layer”里有详细说明，感兴趣的读者可以参考一下。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.3.2、Batch Normalization的评估"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在我们使用Batch Norm层进行实验。  \n",
    "首先，使用MNIST数据集，观察使用Batch Norm层和不使用Batch Norm层时学习的过程会如何变化（源代码在ch06/batch_norm_test.py中），结果如图6-18所示。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_18.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从图6-18的结果可知，使用Batch Norm后，学习进行得更快了。  \n",
    "接着，给予不同的初始值尺度，观察学习的过程如何变化。  \n",
    "图6-19是权重初始值的标准差为各种不同的值时的学习过程图。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们发现，几乎所有的情况下都是使用Batch Norm时学习进行得更快。  \n",
    "同时也可以发现，实际上，在不使用Batch Norm的情况下，如果不赋予一个尺度好的初始值，学习将完全无法进行。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "综上，通过使用Batch Norm，可以推动学习的进行。  \n",
    "并且，对权重初始值变得健壮（“对初始值健壮”表示不那么依赖初始值）。  \n",
    "Batch Norm具备了如此优良的性质，一定能应用在更多场合中。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_19.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.4、正则化"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "机器学习的问题中， **过拟合**是一个很常见的问题。  \n",
    "过拟合指的是只能拟合训练数据，但不能很好地拟合不包含在训练数据中的其他数据的状态。  \n",
    "机器学习的目标是提高泛化能力，即便是没有包含在训练数据里的未观测数据，也希望模型可以进行正确的识别。  \n",
    "我们可以制作复杂的、表现力强的模型，但是相应地，抑制过拟合的技巧也很重要。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.4.1、过拟合"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "发生过拟合的原因，主要有以下两个。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1、模型拥有大量参数、表现力强。  \n",
    "2、训练数据少。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里，我们故意满足这两个条件，制造过拟合现象。  \n",
    "为此，要从MNIST数据集原本的60000个训练数据中只选定300个，并且，为了增加网络的复杂度，使用7层网络（每层有100个神经元，激活函数为ReLU）。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面是用于实验的部分代码（对应文件在ch06/overfit_weight_decay.py\n",
    "中）。  \n",
    "首先是用于读入数据的代码。  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)\n",
    "# 为了再现过拟合，减少学习数据\n",
    "x_train = x_train[:300]\n",
    "t_train = t_train[:300]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "接着是进行训练的代码。  \n",
    "和之前的代码一样，按epoch分别算出所有训练数据和所有测试数据的识别精度。  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100,\n",
    "100, 100, 100], output_size=10)\n",
    "optimizer = SGD(lr=0.01) # 用学习率为0.01的SGD更新参数\n",
    "\n",
    "max_epochs = 201\n",
    "train_size = x_train.shape[0]\n",
    "batch_size = 100\n",
    "\n",
    "train_loss_list = []\n",
    "train_acc_list = []\n",
    "test_acc_list = []\n",
    "\n",
    "iter_per_epoch = max(train_size / batch_size, 1)\n",
    "epoch_cnt = 0\n",
    "\n",
    "for i in range(1000000000):\n",
    "    batch_mask = np.random.choice(train_size, batch_size)\n",
    "    x_batch = x_train[batch_mask]\n",
    "    t_batch = t_train[batch_mask]\n",
    "    \n",
    "    grads = network.gradient(x_batch, t_batch)\n",
    "    optimizer.update(network.params, grads)\n",
    "    \n",
    "    if i % iter_per_epoch == 0:\n",
    "        train_acc = network.accuracy(x_train, t_train)\n",
    "        test_acc = network.accuracy(x_test, t_test)\n",
    "        train_acc_list.append(train_acc)\n",
    "        test_acc_list.append(test_acc)\n",
    "        \n",
    "        epoch_cnt += 1\n",
    "        if epoch_cnt >= max_epochs:\n",
    "            break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "train_acc_list和test_acc_list中以epoch为单位（看完了所有训练数据\n",
    "的单位）保存识别精度。  \n",
    "现在，我们将这些列表（train_acc_list、 test_acc_list）绘成图，结果如图6-20所示。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_20.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "过了 100 个 epoch 左右后，用训练数据测量到的识别精度几乎都为100%。  \n",
    "但是，对于测试数据，离100%的识别精度还有较大的差距。  \n",
    "如此大的识别精度差距，是只拟合了训练数据的结果。  \n",
    "从图中可知，模型对训练时没有使用的一般数据（测试数据）拟合得不是很好。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.4.2、权值衰减"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**权值衰减**是一直以来经常被使用的一种抑制过拟合的方法。  \n",
    "该方法通过在学习的过程中对大的权重进行惩罚，来抑制过拟合。  \n",
    "很多过拟合原本就是因为权重参数取值过大才发生的。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "复习一下，神经网络的学习目的是减小损失函数的值。  \n",
    "这时，例如为损失函数加上权重的平方范数（L2范数）。  \n",
    "这样一来，就可以抑制权重变大。  \n",
    "用符号表示的话，如果将权重记为W， L2范数的权值衰减就是$$\n",
    "\\frac { 1 } { 2 } \\lambda \\boldsymbol { W } ^ { 2 }\n",
    "$$ ，然后将这个$$\n",
    "\\frac { 1 } { 2 } \\lambda \\boldsymbol { W } ^ { 2 }\n",
    "$$加到损失函数上。  \n",
    "这里， λ是控制正则化强度的超参数。  \n",
    "λ设置得越大，对大的权重施加的惩罚就越重。  \n",
    "此外，$$\n",
    "\\frac { 1 } { 2 } \\lambda \\boldsymbol { W } ^ { 2 }\n",
    "$$ 开头的 是用于将$$\n",
    "\\frac { 1 } { 2 } \\lambda \\boldsymbol { W } ^ { 2 }\n",
    "$$的求导结果变成λW的调整用常量。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对于所有权重，权值衰减方法都会为损失函数加上$$\n",
    "\\frac { 1 } { 2 } \\lambda \\boldsymbol { W } ^ { 2 }\n",
    "$$。  \n",
    "因此，在求权重梯度的计算中，要为之前的误差反向传播法的结果加上正则化项的导数λW。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "L2范数相当于各个元素的平方和。用数学式表示的话，假设有权重W = (w1, w2, . . . , wn)，则 L2范数可用$$\n",
    "\\sqrt { w _ { 1 } ^ { 2 } + w _ { 2 } ^ { 2 } + \\cdots + w _ { n } ^ { 2 } }\n",
    "$$计算出来。  \n",
    "除了 L2范数，还有 L1范数、L ∞范数等。  \n",
    "L1范数是各个元素的绝对值之和，相当于 |w1| + |w2| + . . . + |wn|。  \n",
    "L∞范数也称为Max范数，相当于各个元素的绝对值中最大的那一个。  \n",
    "L2范数、L1范数、L∞范数都可以用作正则化项，它们各有各的特点，不过这里我们要实现的是比较常用的L2范数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在我们来进行实验。  \n",
    "对于刚刚进行的实验，应用λ = 0.1的权值衰减，结果如图6-21所示（对应权值衰减的网络在 common/multi_layer_net.py中，用于实验的代码在ch06/overfit_weight_decay.py中）。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_21.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如图6-21所示，虽然训练数据的识别精度和测试数据的识别精度之间有差距，但是与没有使用权值衰减的图6-20的结果相比，差距变小了。  \n",
    "这说明过拟合受到了抑制。  \n",
    "此外，还要注意，训练数据的识别精度没有达到100%（1.0）。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.4.3、Dropout"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "作为抑制过拟合的方法，前面我们介绍了为损失函数加上权重的L2范数的权值衰减方法。  \n",
    "该方法可以简单地实现，在某种程度上能够抑制过拟合。  \n",
    "但是，如果网络的模型变得很复杂，只用权值衰减就难以应对了。  \n",
    "在这种情况下，我们经常会使用Dropout方法。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dropout是一种在学习的过程中随机删除神经元的方法。  \n",
    "训练时，随机选出隐藏层的神经元，然后将其删除。  \n",
    "被删除的神经元不再进行信号的传递，如图6-22所示。  \n",
    "训练时，每传递一次数据，就会随机选择要删除的神经元。  \n",
    "然后，测试时，虽然会传递所有的神经元信号，但是对于各个神经元的输出，要乘上训练时的删除比例后再输出。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_22.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面我们来实现Dropout。  \n",
    "这里的实现重视易理解性。  \n",
    "不过，因为训练时如果进行恰当的计算的话，正向传播时单纯地传递数据就可以了（不用乘以删除比例），所以深度学习的框架中进行了这样的实现。  \n",
    "关于高效的实现，可以参考Chainer中实现的Dropout。  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dropout:\n",
    "    def __init__(self, dropout_ratio=0.5):\n",
    "        self.dropout_ratio = dropout_ratio\n",
    "        self.mask = None\n",
    "    def forward(self, x, train_flg=True):\n",
    "        if train_flg:\n",
    "            self.mask = np.random.rand(*x.shape) > self.dropout_ratio\n",
    "            return x * self.mask\n",
    "        else:\n",
    "            return x * (1.0 - self.dropout_ratio)\n",
    "    def backward(self, dout):\n",
    "        return dout * self.mask"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里的要点是，每次正向传播时， self.mask中都会以False的形式保存要删除的神经元。   \n",
    "self.mask会随机生成和 x形状相同的数组，并将值比dropout_ratio大的元素设为True。  \n",
    "反向传播时的行为和ReLU相同。  \n",
    "也就是说，正向传播时传递了信号的神经元，反向传播时按原样传递信号；正向传播时没有传递信号的神经元，反向传播时信号将停在那里。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "common/trainer.py 中实现了 Trainer 类。  \n",
    "这个类可以负责前面所进行的网络的学习。  \n",
    "详细内容可以参照 common/trainer.py和ch06/overfit_dropout.py。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dropout的实验和前面的实验一样，使用7层网络（每层有100个神经元，激活函数为ReLU），一个使用Dropout，另一个不使用Dropout，实验的结果如图6-23所示。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_23.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "图6-23中，通过使用Dropout，训练数据和测试数据的识别精度的差距变小了。  \n",
    "并且，训练数据也没有到达100%的识别精度。  \n",
    "像这样，通过使用Dropout，即便是表现力强的网络，也可以抑制过拟合。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "机器学习中经常使用集成学习。  \n",
    "所谓集成学习，就是让多个模型单独进行学习，推理时再取多个模型的输出的平均值。  \n",
    "用神经网络的语境来说，比如，准备5个结构相同（或者类似）的网络，分别进行学习，测试时，以这5个网络的输出的平均值作为答案。  \n",
    "实验告诉我们，通过进行集成学习，神经网络的识别精度可以提高好几个百分点。  \n",
    "这个集成学习与 Dropout有密切的关系。  \n",
    "这是因为可以将 Dropout理解为，通过在学习过程中随机删除神经元，从而每一次都让不同\n",
    "的模型进行学习。  \n",
    "并且，推理时，通过对神经元的输出乘以删除比例（比如，0.5等），可以取得模型的平均值。  \n",
    "也就是说，可以理解成，Dropout将集成学习的效果（模拟地）通过一个网络实现了。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.5、超参数的验证"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "神经网络中，除了权重和偏置等参数， **超参数**（hyper-parameter）也经常出现。  \n",
    "这里所说的超参数是指，比如各层的神经元数量、 batch大小、参数更新时的学习率或权值衰减等。  \n",
    "如果这些超参数没有设置合适的值，模型的性能就会很差。  \n",
    "虽然超参数的取值非常重要，但是在决定超参数的过程中一般会伴随很多的试错。  \n",
    "本节将介绍尽可能高效地寻找超参数的值的方法。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.5.1、验证数据"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "之前我们使用的数据集分成了训练数据和测试数据，训练数据用于学习，测试数据用于评估泛化能力。  \n",
    "由此，就可以评估是否只过度拟合了训练数据（是否发生了过拟合），以及泛化能力如何等。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面我们要对超参数设置各种各样的值以进行验证。  \n",
    "**这里要注意的是，不能使用测试数据评估超参数的性能**。<br>\n",
    "这一点非常重要，但也容易被忽视。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为什么不能用测试数据评估超参数的性能呢？  \n",
    "这是因为如果使用测试数据调整超参数，超参数的值会对测试数据发生过拟合。  \n",
    "换句话说，用测试数据确认超参数的值的“好坏”，就会导致超参数的值被调整为只拟合测试数据。  \n",
    "\n",
    "这样的话，可能就会得到不能拟合其他数据、泛化能力低的模型。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "因此，调整超参数时，必须使用超参数专用的确认数据。  \n",
    "用于调整超参数的数据，一般称为**验证数据**（validation data）。  \n",
    "我们使用这个验证数据来评估超参数的好坏。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "训练数据用于参数（权重和偏置）的学习，验证数据用于超参数的性能评估。  \n",
    "为了确认泛化能力，要在最后使用（比较理想的是只用一次）测试数据。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "根据不同的数据集，有的会事先分成训练数据、验证数据、测试数据三部分，有的只分成训练数据和测试数据两部分，有的则不进行分割。  \n",
    "在这种情况下，用户需要自行进行分割。  \n",
    "如果是MNIST数据集，获得验证数据的最简单的方法就是从训练数据中事先分割20%作为验证数据，代码如下所示。  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "(x_train, t_train), (x_test, t_test) = load_mnist()\n",
    "\n",
    "# 打乱训练数据\n",
    "x_train, t_train = shuffle_dataset(x_train, t_train)\n",
    "\n",
    "# 分割验证数据\n",
    "validation_rate = 0.20\n",
    "validation_num = int(x_train.shape[0] * validation_rate)\n",
    "\n",
    "x_val = x_train[:validation_num]\n",
    "t_val = t_train[:validation_num]\n",
    "x_train = x_train[validation_num:]\n",
    "t_train = t_train[validation_num:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里，分割训练数据前，先打乱了输入数据和教师标签。  \n",
    "这是因为数据集的数据可能存在偏向（比如，数据从“0”到“10”按顺序排列等）。  \n",
    "这里使用的 shuffle_dataset函数利用了 np.random.shuffle，在 common/util.py中有它的实现。  \n",
    "接下来，我们使用验证数据观察超参数的最优化方法。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.5.2、超参数的最优化"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "进行超参数的最优化时，逐渐缩小超参数的“好值”的存在范围非常重要。  \n",
    "所谓逐渐缩小范围，是指一开始先大致设定一个范围，从这个范围中随机选出一个超参数（采样），用这个采样到的值进行识别精度的评估；然后，多次重复该操作，观察识别精度的结果，根据这个结果缩小超参数的“好值”的范围。  \n",
    "通过重复这一操作，就可以逐渐确定超参数的合适范围。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "有报告显示，在进行神经网络的超参数的最优化时，与网格搜索等有规律的搜索相比，随机采样的搜索方式效果更好。  \n",
    "这是因为在多个超参数中，各个超参数对最终的识别精度的影响程度不同。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "超参数的范围只要“大致地指定”就可以了。  \n",
    "所谓“大致地指定”，是指像0.001（10−3）到1000（103）这样，以“10的阶乘”的尺度指定范围（也表述为“用对数尺度（log scale）指定”）。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在超参数的最优化中，要注意的是深度学习需要很长时间（比如，几天\n",
    "或几周）。  \n",
    "因此，在超参数的搜索中，需要尽早放弃那些不符合逻辑的超参数。  \n",
    "于是，在超参数的最优化中，减少学习的epoch，缩短一次评估所需的时间是一个不错的办法。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "以上就是超参数的最优化的内容，简单归纳一下，如下所示:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 步骤0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "设定超参数的范围。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 步骤1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从设定的超参数范围中随机采样。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 步骤2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用步骤1中采样到的超参数的值进行学习，通过验证数据评估识别精度（但是要将epoch设置得很小）。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 步骤3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "重复步骤1和步骤2（100次等），根据它们的识别精度的结果，缩小超参数的范围。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "反复进行上述操作，不断缩小超参数的范围，在缩小到一定程度时，从该范围中选出一个超参数的值。  \n",
    "这就是进行超参数的最优化的一种方法。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里介绍的超参数的最优化方法是实践性的方法。  \n",
    "不过，这个方法与其说是科学方法，倒不如说有些实践者的经验的感觉。  \n",
    "在超参数的最优化中，如果需要更精炼的方法，可以使用贝叶斯最优化（Bayesian optimization）。  \n",
    "贝叶斯最优化运用以贝叶斯定理为中心的数学理论，能够更加严密、高效地进行最优化。  \n",
    "详细内容请参 考 论 文“Practical Bayesian Optimization of Machine Learning \n",
    "Algorithms”等。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.5.3、超参数最优化的实现"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在，我们使用MNIST数据集进行超参数的最优化。这里我们将学习率和控制权值衰减强度的系数（下文称为“权值衰减系数”）这两个超参数的搜索问题作为对象。  \n",
    "这个问题的设定和解决思路参考了斯坦福大学的课程“CS231n”。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如前所述，通过从 0.001（10−3）到 1000（103）这样的对数尺度的范围中随机采样进行超参数的验证。  \n",
    "这在Python中可以写成 10 ** np.random.uniform(-3, 3)。  \n",
    "在该实验中，权值衰减系数的初始范围为10−8到10−4，学习率的初始范围为10−6到10−2。  \n",
    "此时，超参数的随机采样的代码如下所示。  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "weight_decay = 10 ** np.random.uniform(-8, -4)\n",
    "lr = 10 ** np.random.uniform(-6, -2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "像这样进行随机采样后，再使用那些值进行学习。  \n",
    "之后，多次使用各种超参数的值重复进行学习，观察合乎逻辑的超参数在哪里。  \n",
    "这里省略了具体实现，只列出了结果。  \n",
    "进行超参数最优化的源代码在 ch06/hyperparameter_optimization.py中，请大家自由参考。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面我们就以权值衰减系数为10−8到10−4、学习率为10−6到10−2的范围进行实验，结果如图6-24所示。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"./imgs/6_24.png\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "图6-24中，按识别精度从高到低的顺序排列了验证数据的学习的变化。  \n",
    "从图中可知，直到“Best-5”左右，学习进行得都很顺利。  \n",
    "因此，我们来观察一下“Best-5”之前的超参数的值（学习率和权值衰减系数），结果如下\n",
    "所示。  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "Best-1 (val acc:0.83) | lr:0.0092, weight decay:3.86e-07\n",
    "Best-2 (val acc:0.78) | lr:0.00956, weight decay:6.04e-07\n",
    "Best-3 (val acc:0.77) | lr:0.00571, weight decay:1.27e-06\n",
    "Best-4 (val acc:0.74) | lr:0.00626, weight decay:1.43e-05\n",
    "Best-5 (val acc:0.73) | lr:0.0052, weight decay:8.97e-06\n",
    "'''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从这个结果可以看出，学习率在0.001到0.01、权值衰减系数在10−8到10−6之间时，学习可以顺利进行。  \n",
    "像这样，观察可以使学习顺利进行的超参数的范围，从而缩小值的范围。  \n",
    "然后，在这个缩小的范围中重复相同的操作。  \n",
    "这样就能缩小到合适的超参数的存在范围，然后在某个阶段，选择一个最终的超参数的值。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.6、小结"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "本章我们介绍了神经网络的学习中的几个重要技巧。  \n",
    "参数的更新方法、权重初始值的赋值方法、 Batch Normalization、 Dropout等，这些都是现代神经网络中不可或缺的技术。  \n",
    "另外，这里介绍的技巧，在最先进的深度学习中也被频繁使用。  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "1、参 数 的 更 新 方 法，除 了 SGD 之 外，还 有 Momentum、 AdaGrad、Adam等方法。\n",
    "2、权重初始值的赋值方法对进行正确的学习非常重要。\n",
    "3、作为权重初始值， Xavier初始值、 He初始值等比较有效。\n",
    "4、通过使用Batch Normalization，可以加速学习，并且对初始值变得健壮。\n",
    "5、抑制过拟合的正则化技术有权值衰减、 Dropout等。\n",
    "6、逐渐缩小“好值”存在的范围是搜索超参数的一个有效方法。\n",
    "'''"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
